package com.boarsoft.boar.soagov.flow.action;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.codehaus.plexus.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.boarsoft.bean.ReplyInfo;
import com.boarsoft.boar.app.AppInstBiz;
import com.boarsoft.boar.common.Constants;
import com.boarsoft.boar.config.service.ConfigVarService;
import com.boarsoft.boar.entity.AppInfo;
import com.boarsoft.boar.entity.AppInst;
import com.boarsoft.boar.entity.SoaNexus;
import com.boarsoft.boar.soagov.entity.ComInfo;
import com.boarsoft.boar.soagov.entity.ObjFaces;
import com.boarsoft.boar.soagov.entity.SvcInfo;
import com.boarsoft.boar.soagov.face.ObjFacesBiz;
import com.boarsoft.boar.soagov.nexus.NexusBiz;
import com.boarsoft.boar.soagov.svc.SvcAppBiz;
import com.boarsoft.boar.soagov.svc.SvcBiz;
import com.boarsoft.common.Authorized;
import com.boarsoft.common.Util;
import com.boarsoft.common.dao.PagedResult;
import com.boarsoft.common.util.Base64;
import com.boarsoft.common.util.BeanUtil;
import com.boarsoft.common.util.FileUtil;
import com.boarsoft.common.util.InetUtil;
import com.boarsoft.common.util.JsonUtil;
import com.boarsoft.flow.core.SimpleFlow;
import com.boarsoft.flow.exchange.FlowExchanger;
import com.boarsoft.flow.store.SimpleFlowStore;
import com.boarsoft.rpc.core.RpcContext;

/**
 * 流程图服务
 *
 * @author Mac_J
 */
@RestController
@RequestMapping("/flow")
public class FlowInfoAction {
	private static final Logger log = LoggerFactory.getLogger(FlowInfoAction.class);

	@Autowired
	private NexusBiz nexusBiz;
	@Autowired
	private ObjFacesBiz objFacesBiz;
	@Autowired
	private SimpleFlowStore flowStore;
	// @Autowired
	// private FlowReferenceBiz flowReferenceBiz;
	@Autowired
	private SvcAppBiz svcAppBiz;
	@Autowired
	private AppInstBiz appInstBiz;
	@Autowired
	@Qualifier("remoteFlowExchanger")
	private FlowExchanger remoteFlowExchanger;
	@Autowired
	@Qualifier("localFlowExchanger")
	private FlowExchanger localFlowExchanger;
	@Autowired
	private SvcBiz svcBiz;
	
	@Autowired(required = false)
	private ConfigVarService cfgVarSvc;

	// /** 服务ID */
	// private String id;
	// /** 服务编号 */
	// private String code;
	// /** 服务名称 */
	// private String name;
	// /** 接口ID */
	// private String faceId;
	// /** 实现类 */
	// private String impl;
	// /** 实现类的版本 */
	// private String version;
	// /** 状态 */
	// private short status = SvcInfo.STATUS_INUSE;
	// /** 所属系统 */
	// private String catalog;
	// /** 是普通服务还是有流程的服务 */
	// private short type = SvcInfo.TYPE_FLOW;
	// /** 访问路径 */
	// private String url;
	// /** 导出路径 */
	// private String path;
	// /** 服务描述 */
	// private String memo;
	// /** 访问路径 */
	// private String thumb;
	// /** 提供服务的应用id */
	// private String appId;
	// /** 标识当前编辑的只是流程，非服务 */
	// private boolean flowOnly = false;
	//
	// private String key;
	//
	// private String flowId;
	// private String nexus; // flow所引用的所有组件
	// private String faces; // flow所涉及的接口
	// private String json;
	// private String data;
	@SuppressWarnings("unchecked")
	@RequestMapping("/save.do")
	@Authorized(code = "flow.save")
	public ReplyInfo<Object> save(SvcInfo a, String thumb, String nexus, String faces, String json) {
		// 更新或保存对象
		String id = a.getId();
		SvcInfo fo = svcBiz.get(id);
		if (fo == null) {
			fo = a;
		} else {
			fo.setFaceId(a.getFaceId());
			fo.setImpl(a.getImpl());
		}
		svcBiz.save(fo);
		id = fo.getId();

		// 保存与组件的关系（先删除，再添加）
		List<SoaNexus> nLt = new ArrayList<SoaNexus>();
		log.info(nexus);
		// List<Map<String, Object>> mLt = new Gson().fromJson(nexus,
		// ArrayList.class);
		List<Map<String, Object>> mLt = JsonUtil.parseObject(nexus, ArrayList.class);
		SoaNexus z = null;
		for (Map<String, Object> m : mLt) {
			z = new SoaNexus();
			BeanUtil.fillObjectWithMap(z, m);
			log.info(JsonUtil.from(z));
			nLt.add(z);
		}
		// 递归保存引用，以及引用的引用
		Map<String, SoaNexus> tMap = new HashMap<String, SoaNexus>();
		nexusBiz.getReferences(z, nLt, tMap);
		List<SoaNexus> tLt = CollectionUtils.iteratorToList(tMap.values().iterator());
		nexusBiz.save(id, tLt); // 此方法会先删除原有的关系
		// 保存与接口的关系
		String[] fa = faces.split(",");
		for (String f : fa) {
			ObjFaces n = new ObjFaces();
			n.setObjId(id);
			n.setObjType(ComInfo.TYPE_SUBFLOW);
			n.setFaceId(f);
			objFacesBiz.save(n);// 此方法会检查objId0和objId1的依赖是否存在
		}
		// 保存图表（此路径与导出路径不同）
		// DirInfo d = dirBiz.get(id);// flowId与dirId相同
		// StringBuilder sb = new StringBuilder();
		// sb.setLength(0);
		// sb.append(Config.PATH_PRIVATE).append(d.getPath());
		// sb.append("/").append(d.getCode()).append("/diagram.js");
		// sb.append(Config.PATH_PRIVATE).append("/").append(id).append("/diagram.js");
		// String pv = sb.toString();
		String pv = flowStore.getDiagramPath(id).concat("/diagram.js");
		log.info("Save flow diagram json to {}", pv);
		try {
			File fj = new File(pv);
			FileUtil.makePath(fj.getParent());
			FileUtil.write(fj, json);
		} catch (Exception e) {
			log.error("Error on save flow diagram json to {}", pv, e);
			return Constants.REPLY_ERROR_INTERNAL;
		}
		// sb.setLength(0);
		// sb.append(Config.PATH_PRIVATE).append(d.getPath());
		// sb.append("/").append(d.getCode()).append("/diagram.png");
		// sb.append(Config.PATH_PRIVATE).append("/").append(id).append("/diagram.png");
		// String pb = sb.toString();
		String pb = flowStore.getDiagramPath(id).concat("/diagram.png");
		String[] pd = thumb.split(",");
		log.info("Save flow diagram thumb to {}", pd, pb);
		try {
			File fp = new File(pb);
			FileUtil.makePath(fp.getParent());
			Files.write(fp.toPath(), Base64.decode(pd[1]));
			return ReplyInfo.SUCCESS;
		} catch (Exception e) {
			log.error("Error on save flow diagram thumb to {}", pb, e);
			return Constants.REPLY_ERROR_INTERNAL;
		}
	}

	@RequestMapping("/load.do")
	@Authorized(code = "flow.load")
	public ReplyInfo<Object> load(String id) {
		String pv = flowStore.getDiagramPath(id).concat("/diagram.js");
		File f = new File(pv);
		log.info("Load flow diagram json from {}", pv);
		try {
			// File f = new File(pv);
			if (f.exists()) {
				String json = FileUtil.read(f);
				return new ReplyInfo<Object>(true, json);
			}
			return ReplyInfo.SUCCESS;
		} catch (Exception e) {
			log.error("Error on load flow diagram json from {}", pv, e);
			return Constants.REPLY_ERROR_INTERNAL;
		}
	}

	@RequestMapping("/thumb.do")
	@Authorized(code = "flow.thumb")
	public ReplyInfo<String> thumb(HttpServletResponse response, String id, String url) {
		// DirInfo d = dirBiz.get(id);// flowId与dirId相同
		// StringBuilder sb = new StringBuilder();
		// sb.append(Config.PATH_PRIVATE).append(d.getPath());
		// sb.append("/").append(d.getCode()).append("/diagram.png");
		// String pv = sb.toString();
		String pv = flowStore.getDiagramPath(id).concat("/diagram.png");
		log.info("Load flow diagram thumb from {}", pv);
		try {
			File f = new File(pv);
			if (f.exists()) {
				byte[] ba = Files.readAllBytes(f.toPath());
				if (Util.strIsEmpty(url)) {
					response.setContentType("image/png");
					ServletOutputStream os = response.getOutputStream();
					os.write(ba);
					response.flushBuffer();
				} else {
					return new ReplyInfo<String>(true, "data:image/png;base64,".concat(Base64.encode(ba)));
				}
			} else {
				return new ReplyInfo<String>(Constants.ERROR_NOTFOUND, "Msg.flow.diagram");
			}
		} catch (Exception e) {
			try {
				response.getOutputStream().close();
			} catch (IOException e1) {
			}
			log.error("Error on load flow diagram thumb from {}", pv, e);
			// return new ReplyInfo<String>(Constants.ERROR_UNKNOWN);
			return new ReplyInfo<String>(Constants.ERROR_UNKNOWN, "Msg.flow.diagram");
		}
		return null;
	}

	/**
	 * 获取post上传的xml数据并在指定的目录生成xml文件 参数：type-deploy
	 * id-9483859e586fd9080158700128c60042 flowId=ReferenceStandardFlow
	 * 以flowId作为文件名
	 * 
	 * @return
	 */
	@RequestMapping("publish.do")
	@Authorized(code = "flow.publish")
	public ReplyInfo<String> publish(String catalog, String id, String data) {
		SvcInfo fo = svcBiz.get(id);
		log.warn("Publish flow {}={}/{}", id, fo.getCatalog(), fo.getCode());
		// 遍历所有需要发布的节点，执行发布动作，调用远程系统的exchange方法，发布此流程
		String svcId = fo.getId();
		Map<String, String> rm = new HashMap<String, String>();
		// 根据SvcInfo.id（也是SvcInfo.id）查询AppSvc表，找到对应的App
		List<AppInfo> appLt = svcAppBiz.list(svcId, AppInfo.STATUS_INUSE);
		for (AppInfo a : appLt) {
			log.info("Publish flow to app {}={}", a.getId(), a.getCode());
			// 推送给配置中心
			if (cfgVarSvc != null) {
				//svc_code 作为变量名
				cfgVarSvc.save(a.getId(), catalog, fo.getCode(), data);
			}
			// 实时推给使用此XML的应用实例
			List<AppInst> aiLt = appInstBiz.list(null, a.getId(), null, AppInst.STATUS_RUNNING);
			for (AppInst ai : aiLt) {
				String addr = ai.getAddr();
				log.info("Publish flow to app inst {}={}", ai.getId(), addr);
				rm.put(addr, fo.getVer());
				RpcContext.specify2(addr);
				try {
					SimpleFlow f = null;
					if (InetUtil.getAddr().equals(addr)) {
						f = localFlowExchanger.exchange(//
								ai.getDeployPath(), fo.getCode(), data);
					} else {
						f = remoteFlowExchanger.exchange(//
								ai.getDeployPath(), fo.getCode(), data);
					}
					if (f == null) {
						rm.put(addr, "Msg.flow.load.failed");
						continue;
					}
					if (f.getVer() == null) {
						if (fo.getVer() == null) {
							rm.put(addr, "Msg.flow.version.none");
							continue;
						}
						rm.put(addr, "none");
					} else {
						if (f.getVer().equals(fo.getVer())) {
							rm.put(addr, "Msg.flow.load.success");
							continue;
						}
						rm.put(addr, "Msg.flow.version.wrong");
					}
				} catch (Exception e) {
					log.error("Error on sync flow service {}/{} flow exchanger at {}", //
							svcId, fo.getCode(), addr, e);
					rm.put(addr, e.getMessage());
				} finally {
					RpcContext.specify2(null);
				}
			}
		}
		String rs = JsonUtil.from(rm);
		log.warn("Result of flow exchange {}/{} = {}", svcId, fo.getCode(), rs);
		return new ReplyInfo<String>(true, rs);
	}

	@RequestMapping("/edit.do")
	// @Authorized(code = "flow.edit")
	public ReplyInfo<Object> edit(HttpServletResponse rsp, SvcInfo o, String appId, boolean flowOnly) {
		svcBiz.save(o);
		if (!flowOnly) {
			// 如前台提交了提供服务的应用ID，则保存应用与流程（服务）的关联关系
			if (Util.strIsNotEmpty(appId)) {
				// 获取提供服务的应用
				svcAppBiz.add(appId, new String[] { o.getId() });
			}
		}
		String url = o.getUrl();
		try {
			rsp.sendRedirect(url);
			return null;
		} catch (IOException e) {
			log.error("Can not redirect to {}", url, e);
		}
		return Constants.REPLY_WARN_INVALID;
	}

	@RequestMapping("/get.do")
	// @Authorized(code = "flow.get")
	public String get(String id) {
		SvcInfo fo = svcBiz.get(id);
		if (fo == null) {
			return JsonUtil.from(new ReplyInfo<String>(Constants.ERROR_NOTFOUND, "Msg.common.flow"));
		}
		StringBuilder sb = new StringBuilder();
		sb.append("{ \"success\": true, \"data\": ");
		sb.append(JsonUtil.from(fo)).append(", \"byMe\": { \"0\": {}");
		List<SoaNexus> byLt = nexusBiz.listByMe(id);
		for (SoaNexus n : byLt) {
			sb.append(", \"").append(n.getObjId1()).append("\": ");
			sb.append(JsonUtil.from(n, "face,obj0,lib"));
		}
		sb.append(" }, \"onMe\": { \"0\": {}");
		List<SoaNexus> onLt = nexusBiz.listOnMe(id);
		for (SoaNexus n : onLt) {
			sb.append(", \"").append(n.getObjId1()).append("\": ");
			sb.append(JsonUtil.from(n, "face,obj1,lib"));
		}
		sb.append(" }, \"faces\": { \"0\": {}");
		PagedResult<ObjFaces> cfPr = objFacesBiz.list(id, "", "", "", "", 1, 9999);
		for (ObjFaces n : cfPr.getList()) {
			sb.append(", \"").append(n.getFaceId()).append("\": ");
			sb.append(JsonUtil.from(n.getFace()));
		}
		sb.append(" } }");
		return sb.toString();
	}
}